// 14 重载运算与类型转换
/**
 * 当运算符被用于类类型的对象时，C++语言允许我们为其指定新的含义；同时，我们也能自定义类类型之间的转换规则。和内置类型的转换一样，类类型转换隐式地将一种类型的对象转换成另一种我们所需类型的对象。
 * 当运算符作用于类类型的运算对象时，可以通过运算符重载重新定义该运算符的含义。明智地使用运算符重载能令我们的程序更易于编写和阅读。
 */

#include <iterator>
#include <vector>
#include <list>
#include <deque>
#include <forward_list>
#include <string>
#include <array>
#include <stack>
#include <queue>
#include <algorithm>
#include <numeric>
using std::swap;
using std::vector, std::list, std::deque, std::forward_list, std::string, std::array, std::stack, std::queue;
#include "../Chapter07/Sales_data.h"
#include <iostream>
using std::begin, std::cbegin, std::end, std::cend, std::find, std::accumulate, std::equal, std::fill, std::fill_n, std::back_inserter;
using std::cin, std::cout, std::endl;
using std::copy, std::replace, std::replace_copy;
#include <map>
#include <set>
#include <unordered_map>
#include <unordered_set>
#include <utility>
#include <memory>
#include <new>
using namespace std;

// 该重载输出运算函数在Sales_data内被声明为友元函数，所以该函数可以访问Sales_data的私有成员
std::ostream &operator<<(std::ostream &os, const Sales_data &item)
{
    os << item.isbn() << " " << item.units_sold << " " << item.revenue << " " << item.avg_price();
    return os;
}
// 该重载输入运算函数在Sales_data内被声明为友元函数，所以该函数可以访问Sales_data的私有成员
istream &operator>>(istream &is, Sales_data &item)
{
    double price; // 不需要初始化，因为我们先将读入数据到price，之后才使用它
    is >> item.bookNo >> item.units_sold >> price;
    if(is)        // 检查是否输入成功
        item.revenue = item.units_sold * price;
    else
        item = Sales_data(); // 输入失败，对象被赋予默认的状态
    return is;
}

int main()
{
    // 14.2 输入和输出运算符
    // 如我们所知，IO标准库分别使用>>和<<执行输入和输出操作。对于这两个运算符来说，IO库定义了用其读写内置类型的版本，而类则需要自定义适合其对象的新版本以支持IO操作。

    // 14.2.1 重载输出运算符<<
    // 通常情况下，输出运算符的第一个形参是一个非常量ostream对象的引用。之所以ostream是非常量是因为向流写入内容会改变其状态；而该形参是引用是因为我们无法直接复制一个ostream对象。
    // 第二个形参一般来说是一个常量的引用，该常量是我们想要打印的类类型。第二个形参是引用的原因是我们希望避免复制实参；而之所以该形参可以是常量是因为（通常情况下）打印对象不会改变对象的内容。
    // 为了与其他输出运算符保持一致，operator<<一般要返回它的ostream形参。

    // 输出运算符尽量减少格式化操作
    // 用于内置类型的输出运算符不太考虑格式化操作，尤其不会打印换行符，用户希望类的输出运算符也像如此行事。
    // 如果运算符打印了换行符，则用户就无法在对象的同一行内接着打印一些描述性的文本了。相反，令输出运算符尽量减少格式化操作可以使用户有权控制输出的细节。
    // 通常，输出运算符应该主要负责打印对象的内容而非控制格式，输出运算符不应该打印换行符。

    // 输入输出运算符必须是非成员函数
    // 与iostream标准库兼容的输入输出运算符必须是普通的非成员函数，而不能是类的成员函数。否则，它们的左侧运算对象将是我们的类的一个对象：
    // 假设输入输出运算符是某个类的成员，则它们也必须是istream或ostream的成员。然而，这两个类属于标准库，并且我们无法给标准库中的类添加任何成员。
    // 因此，如果我们希望为类自定义IO运算符，则必须将其定义成非成员函数。
    // ********当然，IO运算符通常需要读写类的非公有数据成员，所以IO运算符一般被声明为友元（参见7.2.1节，第241页）。********

    // 14.2.2 重载输入运算符>>
    // 通常情况下，输入运算符的第一个形参是运算符将要读取的流的引用，第二个形参是将要读入到的（非常量）对象的引用。该运算符通常会返回某个给定流的引用。
    // 第二个形参之所以必须是个非常量是因为输入运算符本身的目的就是将数据读入到这个对象中。
    // 输入运算符必须处理输入可能失败的情况，而输出运算符不需要。

    // 输入时的错误
    // 在执行输入运算符时可能发生下列错误：
    // · 当流含有错误类型的数据时读取操作可能失败。例如在读取完bookNo后，输入运算符假定接下来读入的是两个数字数据，一旦输入的不是数字数据，则读取操作及后续对流的其他使用都将失败。
    // · 当读取操作到达文件末尾或者遇到输入流的其他错误时也会失败。
    // 在程序中我们没有逐个检查每个读取操作，而是等读取了所有数据后赶在使用这些数据前一次性检查：
    // 当读取操作发生错误时，输入运算符应该负责从错误中恢复。

    // 标示错误
    // 输入运算符也应该设置流的条件状态以标示出失败信息（参见8.1.2节，第279页）
    // 通常情况下，输入运算符只设置failbit。除此之外，设置eofbit表示文件耗尽，而设置badbit表示流被破坏。最好的方式是由IO标准库自己来标示这些错误。

    return 0;
}